package com.ouc.common.utils.nc;


import com.ouc.common.entity.NcData;
import com.ouc.common.entity.NcRecord;
import com.ouc.common.utils.ArrayUtils;
import com.ouc.common.utils.MathUtils;
import org.springframework.lang.Nullable;
import ucar.ma2.InvalidRangeException;
import ucar.ma2.Range;
import ucar.nc2.Attribute;
import ucar.nc2.Variable;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class NcUtil {

    private static final String[] inValid = {"_FillValue", "missing_value"};  //无效值
    private static final String[] scaleFactor = {"scale_factor"}; //缩放值
    private static final String[] addOffset = {"add_offset"}; //偏移量

    /**
     * 获取变量的默认无效值
     * 若无无效值，则返回 NaN
     *
     * @param variable
     * @return
     */
    public static float getFillValue(Variable variable) {
        Attribute attribute = null;
        for (String s : inValid) {
            if (attribute == null) {
                attribute = variable.findAttribute(s);
            } else {
                break;
            }
        }
        if (attribute == null) {
            return Float.NaN;
        }
        return Float.parseFloat(attribute.getValue(0).toString());
    }

    /**
     * 获取变量的默认缩放值
     * 若无缩放值，则返回 1f
     *
     * @param variable
     * @return
     */
    public static float getScaleFactor(Variable variable) {
        Attribute attribute = null;
        for (String s : scaleFactor) {
            if (attribute == null) {
                attribute = variable.findAttribute(s);
            } else {
                break;
            }
        }
        if (attribute == null) {
            return 1;
        }
        return Float.parseFloat(attribute.getValue(0).toString());
    }

    /**
     * 获取变量的默认偏移量
     * 若无偏移量，则返回 0
     *
     * @param variable
     * @return
     */
    public static float getAddOffset(Variable variable) {
        Attribute attribute = null;
        for (String s : addOffset) {
            if (attribute == null) {
                attribute = variable.findAttribute(s);
            } else {
                break;
            }
        }
        if (attribute == null) {
            return 0;
        }
        return Float.parseFloat(attribute.getValue(0).toString());
    }

    /**
     * 读取变量中的全部数据，对于 3 维及以上的数据，是切片的数据
     * 即某一变量的某一层数据
     *
     * @param variable
     * @return 如果是 3 维数据，取 0 维 0 层切片；如果是 4 维数据，取 0 维 0 层、 1 维 0 层切片；
     * @throws IOException           变量读取数据{@code variable.read()}时抛出的异常
     * @throws InvalidRangeException 切片过程中，重新生成 Range 对象时抛出的范围异常，已做保护措施，应该不会抛出该异常
     */
    public static NcData getDataOfSlice(Variable variable) throws IOException, InvalidRangeException {
        return getDataOfSlice(variable, null);
    }

    /**
     * 读取变量中的全部数据，对于 3 维及以上的数据，是切片的数据
     * 即某一变量的某一层数据
     *
     * @param variable    具体的某一变量
     * @param positionOf0 如果是 3 维数据，该参数代表第 0 维切片层数；如果是 4 维数据，该参数代表第 1 维切片层数
     * @return 如果是 3 维数据，取 0 维 positionOf0 层切片；如果是 4 维数据，取 0 维 0 层、1 维 positionOf0 层切片
     * @throws IOException           变量读取数据{@code variable.read()}时抛出的异常
     * @throws InvalidRangeException 切片过程中，重新生成 Range 对象时抛出的范围异常，已做保护措施，应该不会抛出该异常
     */
    public static NcData getDataOfSlice(Variable variable, Integer positionOf0) throws IOException, InvalidRangeException {
        return getDataOfSlice(variable, positionOf0, null);
    }

    /**
     * 读取变量中的全部数据，对于 3 维及以上的数据，是切片的数据
     * 即某一变量的某一层数据
     *
     * @param variable    具体的某一变量
     * @param positionOf0 如果是 3 维数据，该参数代表第 0 维切片层数；如果是 4 维数据，该参数代表第 1 维切片层数
     * @param positionOf1 如果是 3 维数据，不会使用该参数；如果是 4 维数据，该参数代表第 0 维切片层数
     * @return 如果是 3 维数据，取 0 维 positionOf0 层切片；如果是 4 维数据，取 0 维 positionOf1 层、1 维 positionOf0 层切片
     * @throws IOException           变量读取数据{@code variable.read()}时抛出的异常
     * @throws InvalidRangeException 切片过程中，重新生成 Range 对象时抛出的范围异常，已做保护措施，应该不会抛出该异常
     */
    public static NcData getDataOfSlice(Variable variable, Integer positionOf0, Integer positionOf1) throws IOException, InvalidRangeException {
        NcData ncData = new NcData();
        if (variable == null) {
            return ncData;
        }
        ncData.setDataType(variable.getDataType());


        int rank = variable.getRank();    //return shape.length,返回维度个数
        ncData.setDimensions(rank);     //设置维度
        /**
         * 根据维度和变量数据类型的不同，返回不同的数组
         */
        switch (rank) {
            case 1:
                ncData.setData1D(getData1D(variable.read().getStorage()));
                break;
            case 2:
                Object o = variable.read().copyToNDJavaArray();

                float[][] data2D = getData2D(o);
                ncData.setData2D(getData2D(variable.read().copyToNDJavaArray()));
                break;
            case 3:
                //variable.getRanges(),返回决定该变量的各个维度的长度
                List<Range> ranges3D = new ArrayList<>(variable.getRanges());
                Range range3D = ranges3D.get(0);    //返回第 0 维度的长度
                positionOf0 = positionOf0 == null ? 0 : MathUtils.middle(positionOf0, 0, range3D.length() - 1);
                ranges3D.set(0, new Range(range3D.getName(), positionOf0, positionOf0, range3D.stride()));
                ncData.setData2D(getData2D(variable.read(ranges3D).reduce().copyToNDJavaArray()));
                break;
            case 4:
                List<Range> ranges4D = new ArrayList<>(variable.getRanges());
                Range range4D0 = ranges4D.get(0);
                positionOf1 = positionOf1 == null ? 0 : MathUtils.middle(positionOf1, 0, range4D0.length() - 1);
                ranges4D.set(0, new Range(range4D0.getName(), positionOf1, positionOf1, range4D0.stride()));
                Range range4D1 = ranges4D.get(1);
                positionOf0 = positionOf0 == null ? 0 : MathUtils.middle(positionOf0, 0, range4D1.length() - 1);
                ranges4D.set(1, new Range(range4D1.getName(), positionOf0, positionOf0, range4D1.stride()));
                ncData.setData2D(getData2D(variable.read(ranges4D).reduce().copyToNDJavaArray()));
                break;
        }
        //变量再处理
        float fillValue = getFillValue(variable); //获取无效值
        float scaleFactor = getScaleFactor(variable);//获取缩放值
        float addOffset = getAddOffset(variable);//获取偏移量
        reDeal(ncData, fillValue, scaleFactor, addOffset);

        return ncData;
    }

    /**
     * 变量再处理。根据无效值，缩放值，偏移量进行相应的处理
     *
     * @param ncData
     * @param fillValue
     * @param scaleFactor
     * @param addOffset
     */
    private static void reDeal(NcData ncData, float fillValue, float scaleFactor, float addOffset) {
        //不存无效值，缩放值，偏移量
        if (Float.isNaN(fillValue) && scaleFactor == 1 && addOffset == 0) {
            return;
        }
        if (ncData.getData1D() != null) {
            float[] data1D = ncData.getData1D();
            for (int i = 0; i < data1D.length; i++) {
                data1D[i] = data1D[i] == fillValue || Float.isNaN(data1D[i]) ? Float.NaN : data1D[i] * scaleFactor + addOffset;
            }
        } else {
            float[][] data2D = ncData.getData2D();
            for (int i = 0; i < data2D.length; i++) {
                for (int j = 0; j < data2D[i].length; j++) {
                    data2D[i][j] = data2D[i][j] == fillValue || Float.isNaN(data2D[i][j]) ? Float.NaN : data2D[i][j] * scaleFactor + addOffset;
                }
            }
        }
    }

    /**
     * 无效值再处理
     *
     * @param ncRecord    nc记录数据
     * @param fillValue   无效参数
     * @param scaleFactor 要乘的参数
     * @param addOffset   要加的参数
     */
    private static void reDeal(NcRecord ncRecord, float fillValue, float scaleFactor, float addOffset) {
        //不存无效值，缩放值，偏移量
        if (Float.isNaN(fillValue) && scaleFactor == 1 && addOffset == 0) {
            return;
        }
        if (ncRecord.getData1D() != null) {
            float[] data1D = ncRecord.getData1D();
            for (int i = 0; i < data1D.length; i++) {
                data1D[i] = data1D[i] == fillValue || Float.isNaN(data1D[i]) ? Float.NaN : data1D[i] * scaleFactor + addOffset;
            }
        } else if (ncRecord.getData2D() != null) {
            float[][] data2D = ncRecord.getData2D();
            for (int i = 0; i < data2D.length; i++) {
                for (int j = 0; j < data2D[i].length; j++) {
                    data2D[i][j] = data2D[i][j] == fillValue || Float.isNaN(data2D[i][j]) ? Float.NaN : data2D[i][j] * scaleFactor + addOffset;
                }
            }
        } else if (ncRecord.getData3D() != null) {
            float[][][] data3D = ncRecord.getData3D();
            for (int i = 0; i < data3D.length; i++) {
                for (int j = 0; j < data3D[i].length; j++) {
                    for (int k = 0; k < data3D[i][j].length; k++) {
                        data3D[i][j][k] = data3D[i][j][k] == fillValue || Float.isNaN(data3D[i][j][k]) ? Float.NaN : data3D[i][j][k] * scaleFactor + addOffset;
                    }
                }
            }
        }
    }

    @Nullable
    public static float[] getData1D(Object obj) {
        float[] result = null;
        if (obj instanceof byte[]) {
            result = ArrayUtils.transformByte1D((byte[]) obj);
        } else if (obj instanceof short[]) {
            result = ArrayUtils.transformShort1D((short[]) obj);
        } else if (obj instanceof int[]) {
            result = ArrayUtils.transformInt1D((int[]) obj);
        } else if (obj instanceof long[]) {
            result = ArrayUtils.transformLong1D((long[]) obj);
        } else if (obj instanceof float[]) {
            result = (float[]) obj;
        } else if (obj instanceof double[]) {
            result = ArrayUtils.transformDouble1D((double[]) obj);
        }
        return result;
    }

    @Nullable
    private static float[][] getData2D(Object obj) {
        float[][] result = null;
        if (obj instanceof byte[][]) {
            result = ArrayUtils.transformByte2D((byte[][]) obj);
        } else if (obj instanceof short[][]) {
            result = ArrayUtils.transformShort2D((short[][]) obj);
        } else if (obj instanceof int[][]) {
            result = ArrayUtils.transformInt2D((int[][]) obj);
        } else if (obj instanceof long[][]) {
            result = ArrayUtils.transformLong2D((long[][]) obj);
        } else if (obj instanceof float[][]) {
            result = (float[][]) obj;
        } else if (obj instanceof double[][]) {
            result = ArrayUtils.transformDouble2D((double[][]) obj);
        }
        return result;
    }

    @Nullable
    public static float[][][] getData3D(Object obj) {
        float[][][] result = null;
        if (obj instanceof byte[][][]) {
            result = ArrayUtils.transformByte3D((byte[][][]) obj);
        } else if (obj instanceof short[][][]) {
            result = ArrayUtils.transformShort3D((short[][][]) obj);
        } else if (obj instanceof int[][][]) {
            result = ArrayUtils.transformInt3D((int[][][]) obj);
        } else if (obj instanceof long[][][]) {
            result = ArrayUtils.transformLong3D((long[][][]) obj);
        } else if (obj instanceof float[][][]) {
            result = (float[][][]) obj;
        } else if (obj instanceof double[][][]) {
            result = ArrayUtils.transformDouble3D((double[][][]) obj);
        }
        return result;
    }

    /**
     * 获取所有层级的数据
     * 因为NCData只有一维数据二维数据
     * 之后可以进行合并
     * 但是为了防止之前的代码出问题
     * 这里只能通过增加功能增加实体来防止改动之前代码
     *
     * @param variable 变量
     * @return
     * @throws IOException
     * @throws InvalidRangeException
     */
    public static NcRecord getAllDataOfVariable(Variable variable) throws IOException, InvalidRangeException {
        NcRecord ncRecord = new NcRecord();
        if (variable == null) {
            return ncRecord;
        }
        ncRecord.setDataType(variable.getDataType());
        int rank = variable.getRank();    //return shape.length,返回维度个数
        ncRecord.setDimension(rank);     //设置维度
        switch (rank) {
            case 1:
                ncRecord.setData1D(getData1D(variable.read().getStorage()));
                break;
            case 2:
                ncRecord.setData2D(getData2D(variable.read().copyToNDJavaArray()));
                break;
            case 3:
                //variable.getRanges(),返回决定该变量的各个维度的长度
                List<Range> ranges3D = new ArrayList<>(variable.getRanges());
                ncRecord.setData2D(getData2D(variable.read(ranges3D).reduce().copyToNDJavaArray()));
                break;
            case 4:
                List<Range> ranges4D = new ArrayList<>(variable.getRanges());
                ncRecord.setData3D(getData3D(variable.read(ranges4D).reduce().copyToNDJavaArray()));
                break;
        }
        ncRecord.setVariableName(variable.getShortName());
        //变量再处理
        float fillValue = getFillValue(variable); //获取无效值
        float scaleFactor = getScaleFactor(variable);//获取缩放值
        float addOffset = getAddOffset(variable);//获取偏移量
        reDeal(ncRecord, fillValue, scaleFactor, addOffset);
        return ncRecord;
    }

    public static NcRecord getAllDataOfVariableWithLimit(Variable variable, Integer[] range0, Integer[] range1, Integer positionOf0) throws IOException, InvalidRangeException {
        NcRecord ncRecord = new NcRecord();
        if (variable == null) {
            return ncRecord;
        }
        ncRecord.setDataType(variable.getDataType());
        int rank = variable.getRank();    //return shape.length,返回维度个数
        ncRecord.setDimension(rank);     //设置维度
        ArrayList<Range> ranges = new ArrayList<>(variable.getRanges());
        int[] origin = new int[rank];
        int[] s = new int[rank];
        for (int i = 0; i < s.length; i++) {
            s[i] = ranges.get(i).length();  //数组 s 赋初值，默认全部间隔
        }
        switch (rank) {
            case 1:
                if (range0 != null) {
                    origin[rank - 1] = MathUtils.middle(range0[0], 0, s[rank - 1]);
                    s[rank - 1] = MathUtils.middle(range0[1], 1, s[rank - 1] - origin[rank - 1]);
                }
                ncRecord.setData1D(getData1D(variable.read(origin, s).getStorage()));
                break;
            case 2:
                if (range0 != null) {
                    origin[1] = MathUtils.middle(range0[0], 0, s[1]);
                    s[1] = MathUtils.middle(range0[1], 1, s[1] - origin[1]);
                }
                if (range1 != null) {
                    origin[0] = MathUtils.middle(range1[0], 0, s[0]);
                    s[0] = MathUtils.middle(range1[1], 1, s[0] - origin[0]);
                }
                ncRecord.setData2D(getData2D(variable.read(origin, s).copyToNDJavaArray()));
                break;
            case 3:
                if (range0 != null) {
                    origin[2] = MathUtils.middle(range0[0], 0, s[2]);
                    s[2] = MathUtils.middle(range0[1], 1, s[2] - origin[2]);
                }
                if (range1 != null) {
                    origin[1] = MathUtils.middle(range1[0], 0, s[1]);
                    s[1] = MathUtils.middle(range1[1], 1, s[1] - origin[1]);
                }
                if (s[0] == 1) {
                    ncRecord.setData2D(getData2D(variable.read(origin, s).reduce(0).copyToNDJavaArray()));
                } else {
                    ncRecord.setData3D(getData3D(variable.read(origin, s).copyToNDJavaArray()));
                }
                break;
            case 4:
                if (range0 != null) {
                    origin[3] = MathUtils.middle(range0[0], 0, s[3]);
                    s[3] = MathUtils.middle(range0[1], 1, s[3] - origin[3]);
                }
                if (range1 != null) {
                    origin[2] = MathUtils.middle(range1[0], 0, s[2]);
                    s[2] = MathUtils.middle(range1[1], 1, s[2] - origin[2]);
                }
                origin[1] = 0;
                origin[0] = positionOf0 == null ? 0 : positionOf0;
                s[0] = 1;
                ncRecord.setData3D(getData3D(variable.read(origin, s).reduce(0).copyToNDJavaArray()));
                break;
        }
        ncRecord.setVariableName(variable.getShortName());
        //变量再处理
        float fillValue = getFillValue(variable); //获取无效值
        float scaleFactor = getScaleFactor(variable);//获取缩放值
        float addOffset = getAddOffset(variable);//获取偏移量
        reDeal(ncRecord, fillValue, scaleFactor, addOffset);
        return ncRecord;
    }

    public static NcRecord getAllDataOfVariableWithLimit(Variable variable, Integer[] range0, Integer[] range1) throws IOException, InvalidRangeException {
        return getAllDataOfVariableWithLimit(variable, range0, range1, null);
    }

    /**
     * 读取变量集合中的全部数据，对于 3 维及以上的数据，是切片的数据
     *
     * @param variables 变量列表
     * @return 如果是 3 维数据，取 0 维 0 层切片；如果是 4 维数据，取 0 维 0 层、 1 维 0 层切片；
     * @throws IOException           变量读取数据 {@code variable.read()} 时抛出的异常
     * @throws InvalidRangeException 切片过程中，重新生成 Range 对象时抛出的范围异常，已做保护措施，应该不会抛出该异常
     */
    public static Map<String, NcData> getAllDataOfSlice(List<Variable> variables) throws IOException, InvalidRangeException {
        return getAllDataOfSlice(variables, null);
    }

    /**
     * 读取变量集合中的全部数据，对于 3 维及以上的数据，是切片的数据
     *
     * @param variables   变量列表
     * @param positionOf0 如果是 3 维数据，该参数代表第 0 维切片层数；如果是 4 维数据，该参数代表第 1 维切片层数
     * @return 如果是 3 维数据，取 0 维 positionOf0 层切片；如果是 4 维数据，取 0 维 0 层、1 维 positionOf0 层切片
     * @throws IOException           变量读取数据 {@code variable.read()} 时抛出的异常
     * @throws InvalidRangeException 切片过程中，重新生成 Range 对象时抛出的范围异常，已做保护措施，应该不会抛出该异常
     */
    public static Map<String, NcData> getAllDataOfSlice(List<Variable> variables, Integer positionOf0) throws IOException, InvalidRangeException {
        return getAllDataOfSlice(variables, positionOf0, null);
    }

    /**
     * 读取变量集合中的全部数据，对于 3 维及以上的数据，是切片的数据
     *
     * @param variables   变量列表
     * @param positionOf0 如果是 3 维数据，该参数代表第 0 维切片层数；如果是 4 维数据，该参数代表第 1 维切片层数
     * @param positionOf1 如果是 3 维数据，不会使用该参数；如果是 4 维数据，该参数代表第 0 维切片层数
     * @return 如果是 3 维数据，取 0 维 positionOf0 层切片；如果是 4 维数据，取 0 维 positionOf1 层、1 维 positionOf0 层切片
     * @throws IOException           变量读取数据 {@code variable.read()} 时抛出的异常
     * @throws InvalidRangeException 切片过程中，重新生成 Range 对象时抛出的范围异常，已做保护措施，应该不会抛出该异常
     */
    public static Map<String, NcData> getAllDataOfSlice(List<Variable> variables, Integer positionOf0, Integer positionOf1) throws IOException, InvalidRangeException {
        Map<String, NcData> map = new HashMap<>();
        for (Variable variable : variables) {
            map.put(variable.getFullName(), getDataOfSlice(variable, positionOf0, positionOf1));
        }
        return map;
    }

    /**
     * 设置维度条件，读取变量中的部分数据
     * 维度为 1，至多限制一个维度条件；
     * 维度为 大于等于 2，至多限制两个维度条件；(范围限制条件至多两个，避免产生三维数组)
     *
     * @param variable 变量
     * @return 返回带有限制条件的 nc 变量数据
     * @throws IOException           变量读取数据{@code variable.read()}时抛出的异常
     * @throws InvalidRangeException 切片过程中，重新生成 Range 对象时抛出的范围异常，已做保护措施，应该不会抛出该异常
     */
    public static NcData getDataWithLimit(Variable variable) throws IOException, InvalidRangeException {
        return getDataWithLimit(variable, null);
    }

    /**
     * 设置维度条件，读取变量中的部分数据
     * 维度为 1，至多限制一个维度条件；
     * 维度为 大于等于 2，至多限制两个维度条件；(范围限制条件至多两个，避免产生三维数组)
     *
     * @param variable 变量
     * @param range0   限制最后一个维度范围  [origin,s]
     * @return 返回带有限制条件的 nc 变量数据
     * @throws IOException           变量读取数据{@code variable.read()}时抛出的异常
     * @throws InvalidRangeException 切片过程中，重新生成 Range 对象时抛出的范围异常，已做保护措施，应该不会抛出该异常
     */
    public static NcData getDataWithLimit(Variable variable, Integer[] range0) throws IOException, InvalidRangeException {
        return getDataWithLimit(variable, range0, null);
    }

    /**
     * 设置维度条件，读取变量中的部分数据
     * 维度为 1，至多限制一个维度条件；
     * 维度为 大于等于 2，至多限制两个维度条件；(范围限制条件至多两个，避免产生三维数组)
     *
     * @param variable 变量
     * @param range0   限制最后一个维度范围  [origin,s]
     * @param range1   限制倒数第二个维度范围  [origin,s]
     * @return 返回带有限制条件的 nc 变量数据
     * @throws IOException           变量读取数据{@code variable.read()}时抛出的异常
     * @throws InvalidRangeException 切片过程中，重新生成 Range 对象时抛出的范围异常，已做保护措施，应该不会抛出该异常
     */
    public static NcData getDataWithLimit(Variable variable, Integer[] range0, Integer[] range1) throws IOException, InvalidRangeException {
        return getDataWithLimit(variable, range0, range1, null);
    }

    /**
     * 设置维度条件，读取变量中的部分数据
     * 维度为 1，至多限制一个维度条件；
     * 维度为 大于等于 2，至多限制两个维度条件；(范围限制条件至多两个，避免产生三维数组)
     * positionOf0 当维度＞=3时使用，表示倒数第三个维度。三维表示第0维，四维表示第1维。
     * 仅能对三维进行限制（三维，第0维仅能限制获取position0层）四维默认第0维第0层,第1维第positionOf0层
     *
     * @param variable    变量
     * @param range0      限制最后一个维度范围  [origin,s]
     * @param range1      限制倒数第二个维度范围  [origin,s]
     * @param positionOf0 当维度＞=3时使用，表示倒数第三个维度。三维表示第0维，四维表示第1维。
     * @return 返回带有限制条件的 nc 变量数据
     * @throws IOException           变量读取数据{@code variable.read()}时抛出的异常
     * @throws InvalidRangeException 切片过程中，重新生成 Range 对象时抛出的范围异常，已做保护措施，应该不会抛出该异常
     */
    public static NcData getDataWithLimit(Variable variable, Integer[] range0, Integer[] range1, Integer positionOf0) throws IOException, InvalidRangeException {

        return getDataWithLimit(variable, range0, range1, positionOf0, null);
    }

    /**
     * 获取经纬度限制的数据
     *
     * @param variable    变量
     * @param range0      第一维（一般是经度） 包含起始和步进
     * @param range1      第二维（一般是纬度） 包含起始和步进
     * @param positionOf0 第三维限制条件
     * @param positionOf1 第四维限制条件
     * @return
     * @throws IOException
     * @throws InvalidRangeException
     */
    public static NcData getDataWithLimit(Variable variable, Integer[] range0, Integer[] range1, Integer positionOf0, Integer positionOf1) throws IOException, InvalidRangeException {
        NcData ncData = new NcData();
        if (variable == null) {
            return ncData;
        }
        ncData.setDataType(variable.getDataType());

        int rank = variable.getRank();    //return shape.length,返回维度个数
        ncData.setDimensions(rank);     //设置维度

        ArrayList<Range> ranges = new ArrayList<>(variable.getRanges());
        int[] origin = new int[rank];
        int[] s = new int[rank];
        for (int i = 0; i < s.length; i++) {
            s[i] = ranges.get(i).length();  //数组 s 赋初值，默认全部间隔
        }
        /**
         * 根据维度和变量数据类型的不同，返回不同的数组
         */
        switch (rank) {
            case 1:
                if (range0 != null) {
                    origin[rank - 1] = MathUtils.middle(range0[0], 0, s[rank - 1]);
                    s[rank - 1] = MathUtils.middle(range0[1], 1, s[rank - 1] - origin[rank - 1]);
                }
                ncData.setData1D(getData1D(variable.read(origin, s).getStorage()));
                break;
            case 2:
                if (range0 != null) {
                    origin[1] = MathUtils.middle(range0[0], 0, s[1]);
                    s[1] = MathUtils.middle(range0[1], 1, s[1] - origin[1]);
                }
                if (range1 != null) {
                    origin[0] = MathUtils.middle(range1[0], 0, s[0]);
                    s[0] = MathUtils.middle(range1[1], 1, s[0] - origin[0]);
                }
                ncData.setData2D(getData2D(variable.read(origin, s).copyToNDJavaArray()));
                break;
            case 3:
                if (range0 != null) {
                    origin[2] = MathUtils.middle(range0[0], 0, s[2]);
                    s[2] = MathUtils.middle(range0[1], 1, s[2] - origin[2]);
                }
                if (range1 != null) {
                    origin[1] = MathUtils.middle(range1[0], 0, s[1]);
                    s[1] = MathUtils.middle(range1[1], 1, s[1] - origin[1]);
                }
                origin[0] = positionOf0 == null ? 0 : MathUtils.middle(0, positionOf0, ranges.get(0).length() - 1);
                s[0] = 1;
                ncData.setData2D(getData2D(variable.read(origin, s).reduce(0).copyToNDJavaArray()));
                break;
            case 4:
                if (range0 != null) {
                    origin[3] = MathUtils.middle(range0[0], 0, s[3]);
                    s[3] = MathUtils.middle(range0[1], 1, s[3] - origin[3]);
                }
                if (range1 != null) {
                    origin[2] = MathUtils.middle(range1[0], 0, s[2]);
                    s[2] = MathUtils.middle(range1[1], 1, s[2] - origin[2]);
                }
                origin[1] = positionOf0 == null ? 0 : MathUtils.middle(0, positionOf0, ranges.get(1).length() - 1);
                s[1] = 1;
                origin[0] = positionOf1 == null ? 0 : MathUtils.middle(0, positionOf1, ranges.get(0).length() - 1);
                s[0] = 1;
                ncData.setData2D(getData2D(variable.read(origin, s).reduce(0).reduce(0).copyToNDJavaArray()));
                break;
        }
        //变量再处理
        float fillValue = getFillValue(variable); //获取无效值
        float scaleFactor = getScaleFactor(variable);//获取缩放值
        float addOffset = getAddOffset(variable);//获取偏移量
        reDeal(ncData, fillValue, scaleFactor, addOffset);

        return ncData;
    }

    /**
     * 设置维度条件，读取变量列表中的部分数据
     * 维度为 1，至多限制一个维度条件；
     * 维度为 大于等于 2，至多限制两个维度条件；(范围限制条件至多两个，避免产生三维数组)
     *
     * @param variables 变量列表
     * @return 返回带有限制条件的 nc 变量数据
     * @throws IOException           变量读取数据{@code variable.read()}时抛出的异常
     * @throws InvalidRangeException 切片过程中，重新生成 Range 对象时抛出的范围异常，已做保护措施，应该不会抛出该异常
     */
    public static Map<String, NcData> getAllDataWithLimit(List<Variable> variables) throws IOException, InvalidRangeException {
        return getAllDataWithLimit(variables, null);
    }

    /**
     * 设置维度条件，读取变量列表中的部分数据
     * 维度为 1，至多限制一个维度条件；
     * 维度为 大于等于 2，至多限制两个维度条件；(范围限制条件至多两个，避免产生三维数组)
     *
     * @param variables 变量列表
     * @param range0    限制最后一个维度范围  [origin,s]
     * @return 返回带有限制条件的 nc 变量数据
     * @throws IOException           变量读取数据{@code variable.read()}时抛出的异常
     * @throws InvalidRangeException 切片过程中，重新生成 Range 对象时抛出的范围异常，已做保护措施，应该不会抛出该异常
     */
    public static Map<String, NcData> getAllDataWithLimit(List<Variable> variables, Integer[] range0) throws IOException, InvalidRangeException {
        return getAllDataWithLimit(variables, range0, null);
    }

    /**
     * 设置维度条件，读取变量中的部分数据
     * 维度为 1，至多限制一个维度条件；
     * 维度为 大于等于 2，至多限制两个维度条件；(范围限制条件至多两个，避免产生三维数组)
     *
     * @param variables 变量列表
     * @param range0    限制最后一个维度范围  [origin,s]
     * @param range1    限制倒数第二个维度范围  [origin,s]
     * @return 返回带有限制条件的 nc 变量数据
     * @throws IOException           变量读取数据{@code variable.read()}时抛出的异常
     * @throws InvalidRangeException 切片过程中，重新生成 Range 对象时抛出的范围异常，已做保护措施，应该不会抛出该异常
     */
    public static Map<String, NcData> getAllDataWithLimit(List<Variable> variables, Integer[] range0, Integer[] range1) throws IOException, InvalidRangeException {
        return getAllDataWithLimit(variables, range0, range1, null);
    }

    /**
     * 设置维度条件，读取变量列表中的部分数据
     * 维度为 1，至多限制一个维度条件；
     * 维度为 大于等于 2，至多限制两个维度条件；(范围限制条件至多两个，避免产生三维数组)
     * positionOf0 当维度＞=3时使用，表示倒数第三个维度。三维表示第0维，四维表示第1维。
     * 仅能对三维进行限制（三维，第0维仅能限制获取position0层）四维默认第0维第0层,第1维第positionOf0层
     *
     * @param variables   变量列表
     * @param range0      限制最后一个维度范围  [origin,s]
     * @param range1      限制倒数第二个维度范围  [origin,s]
     * @param positionOf0 当维度＞=3时使用，表示倒数第三个维度。三维表示第0维，四维表示第1维。
     * @return 返回带有限制条件的 nc 变量数据
     * @throws IOException           变量读取数据{@code variable.read()}时抛出的异常
     * @throws InvalidRangeException 切片过程中，重新生成 Range 对象时抛出的范围异常，已做保护措施，应该不会抛出该异常
     */
    public static Map<String, NcData> getAllDataWithLimit(List<Variable> variables, Integer[] range0, Integer[] range1, Integer positionOf0) throws IOException, InvalidRangeException {
        Map<String, NcData> map = new HashMap<>();
        for (Variable variable : variables) {
            map.put(variable.getFullName(), getDataWithLimit(variable, range0, range1, positionOf0));
        }
        return map;
    }

    /**
     * 获取深度数组
     *
     * @param level 深度变量
     * @return 深度数组
     * @throws IOException
     */
    public static float[] getLevel(Variable level) throws IOException {
        return getData1D(level.read().getStorage());
    }

    private static final double EARTH_RADIUS = 6371000.0;
    private static final double LAMBDA_ZERO = Math.toRadians(15.0);
    private static final double PHI_ZERO = Math.toRadians(40.0);

    public static double[] toXY(double latitude, double longitude) {
        double phi = Math.toRadians(latitude);
        double lambda = Math.toRadians(longitude);
        double phi_zero = PHI_ZERO;
        double x = EARTH_RADIUS * Math.sqrt(2.0) * Math.sin(lambda - LAMBDA_ZERO) * Math.cos(phi_zero) / Math.sqrt(1.0 + Math.sin(phi_zero) * Math.sin(phi) + Math.cos(phi_zero) * Math.cos(phi) * Math.cos(lambda - LAMBDA_ZERO));
        double y = EARTH_RADIUS * Math.sqrt(2.0) * (Math.cos(phi) * Math.sin(phi_zero) - Math.sin(phi) * Math.cos(phi_zero) * Math.cos(lambda - LAMBDA_ZERO)) / Math.sqrt(1.0 + Math.sin(phi_zero) * Math.sin(phi) + Math.cos(phi_zero) * Math.cos(phi) * Math.cos(lambda - LAMBDA_ZERO));
        return new double[]{x, y};
    }

    public static double[] toLonLat(double x, double y) {
        double rho = Math.sqrt(x * x + y * y);
        double c = 2.0 * Math.asin(rho / (2.0 * EARTH_RADIUS));
        double phi = Math.asin(Math.cos(c) * Math.sin(PHI_ZERO) + y * Math.sin(c) * Math.cos(PHI_ZERO) / rho);
        double lambda = LAMBDA_ZERO + Math.atan(x * Math.sin(c) / (rho * Math.cos(PHI_ZERO) * Math.cos(c) - y * Math.sin(PHI_ZERO) * Math.sin(c)));
        return new double[]{Math.toDegrees(phi), Math.toDegrees(lambda)};
    }
}
